package com.kds.smarthome.controller;

import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.code.kaptcha.Producer;
import com.kds.smarthome.annotation.Log;
import com.kds.smarthome.common.AjaxResponse;
import com.kds.smarthome.config.SmartHomeConfig;
import com.kds.smarthome.controller.vo.CaptchaRequest;
import com.kds.smarthome.controller.vo.LoginRequest;
import com.kds.smarthome.entity.LoginInfo;
import com.kds.smarthome.entity.User;
import com.kds.smarthome.security.jwt.TokenProvider;
import com.kds.smarthome.service.LoginInfoService;
import com.kds.smarthome.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.*;

@Slf4j
@RestController
@RequestMapping("/api/auth")
public class LoginController {

    @Autowired
    private Producer captchaProducer;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private UserService userService;

    @Autowired
    private TokenProvider tokenProvider;

    @Autowired
    private SmartHomeConfig smartHomeConfig;

    @Autowired
    private LoginInfoService loginInfoService;

    @Autowired
    @Qualifier("ipLocationThreadPool")
    private ExecutorService ipLocationThreadPool;

    private Long incrementLoginAttempts(String username) {
        String attemptKey = "user:login:attempt:" + username;
        Long attempts = redisTemplate.opsForValue().increment(attemptKey);

        // 如果是第一次失败，设置过期时间
        if (attempts == 1) {
            // 设置24小时后过期，防止记录永久存在
            redisTemplate.expire(attemptKey, 1, TimeUnit.DAYS);
        }

        log.debug("User: {} failed login attempts: {}", username, attempts);
        return attempts;
    }

    private void clearLoginAttempts(String username) {
        String attemptKey = "user:login:attempt:" + username;
        redisTemplate.delete(attemptKey);
        log.debug("Cleared login attempts for user: {}", username);
    }

    /**
     * 获取客户端IP地址
     */
    private String getIpAddr(HttpServletRequest request) {
        String ip = null;

        // 检查各种可能的请求头
        String[] headers = new String[] {
            "x-forwarded-for", "Proxy-Client-IP", "WL-Proxy-Client-IP",
            "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR", "X-Real-IP"
        };

        for (String header : headers) {
            ip = request.getHeader(header);
            if (!StrUtil.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
                break;
            }
        }

        // 如果所有请求头都没有获取到，使用 RemoteAddr
        if (StrUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
            // 处理本地访问
            if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
                try {
                    // 获取本机IP
                    InetAddress inet = InetAddress.getLocalHost();
                    ip = inet.getHostAddress();
                } catch (UnknownHostException e) {
                    log.error("获取本机IP失败", e);
                }
            }
        }

        // 处理多IP的情况，取第一个非unknown的IP
        if (!StrUtil.isEmpty(ip) && ip.contains(",")) {
            ip = ip.split(",")[0];
        }

        return ip;
    }

    /**
     * 获取IP归属地
     * 使用共享线程池进行异步处理，有超时控制
     * @param ip IP地址
     * @return IP地址归属地信息，如果获取失败则返回null
     */
    private String getIpLocation(String ip) {
        // 如果未启用IP定位或IP为空，直接返回null
        if (!smartHomeConfig.getIpLocation().isEnable() || StrUtil.isEmpty(ip)) {
            return null;
        }

        // 如果是内网IP，直接返回
        if (NetUtil.isInnerIP(ip)) {
            return "内网IP";
        }

        // 使用共享线程池进行异步处理
        Future<String> future = ipLocationThreadPool.submit(() -> {
            try {
                // 根据配置构建URL
                String baseUrl = smartHomeConfig.getIpLocation().getUrl();
                String paramName = smartHomeConfig.getIpLocation().getParamName();
                String url = baseUrl + "?" + paramName + "=" + ip;
                
                String response = HttpUtil.get(url, smartHomeConfig.getIpLocation().getTimeout());
                
                if (StrUtil.isNotBlank(response)) {
                    try {
                        JSONObject jsonObject = JSONUtil.parseObj(response);
                        
                        // 提取省份和城市信息，不同API可能有不同的字段名
                        String province = jsonObject.getStr("prov");
                        String city = jsonObject.getStr("city");
                        
                        if (StrUtil.isNotBlank(province) || StrUtil.isNotBlank(city)) {
                            return String.format("%s %s", 
                                StrUtil.nullToEmpty(province), 
                                StrUtil.nullToEmpty(city)).trim();
                        }
                    } catch (Exception e) {
                        log.error("解析IP归属地响应出错: {}", e.getMessage());
                    }
                }
            } catch (Exception e) {
                log.error("获取IP归属地信息出错: {}", e.getMessage());
            }
            return null;
        });

        try {
            // 等待结果，设置超时
            return future.get(smartHomeConfig.getIpLocation().getTimeout(), TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
            log.warn("获取IP归属地超时: {}", ip);
            future.cancel(true);
        } catch (Exception e) {
            log.error("获取IP归属地异常: {}", e.getMessage());
        }
        
        return null;
    }

    @Log(title = "用户登录", businessType = 1, isSaveRequestData = false)
    @PostMapping("/login")
    public AjaxResponse<?> login(@RequestBody LoginRequest loginRequest, HttpServletRequest request) {
        log.info("username:{},password:{}", loginRequest.getUsername(), "******");

        // 解析User-Agent
        String userAgentStr = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgentUtil.parse(userAgentStr);

        // 准备登录信息
        LoginInfo loginInfo = new LoginInfo();
        loginInfo.setLoginName(loginRequest.getUsername());
        loginInfo.setIpaddr(getIpAddr(request));
        loginInfo.setLoginLocation(getIpLocation(loginInfo.getIpaddr()));
        loginInfo.setBrowser(userAgent.getBrowser().getName());
        loginInfo.setOs(userAgent.getOs().getName());
        loginInfo.setLoginTime(new Date());

        // 1. 检查用户是否被锁定
        String lockKey = "user:login:lock:" + loginRequest.getUsername();

        Boolean isLocked = redisTemplate.hasKey(lockKey);
        if (Boolean.TRUE.equals(isLocked)) {
            loginInfo.setStatus(LoginInfo.STATUS_FAIL);
            loginInfo.setMsg("账户已被锁定，请稍后再试");
            loginInfoService.insertLoginInfo(loginInfo);
            return AjaxResponse.error("账户已被锁定，请稍后再试");
        }

        // 2. 验证验证码
        String storedCaptcha = redisTemplate.opsForValue().get("captcha:" + loginRequest.getCaptchaKey());
        if (storedCaptcha == null) {
            loginInfo.setStatus(LoginInfo.STATUS_FAIL);
            loginInfo.setMsg("验证码已过期");
            loginInfoService.insertLoginInfo(loginInfo);
            return AjaxResponse.error("验证码已过期");
        }
        if (!storedCaptcha.equalsIgnoreCase(loginRequest.getCaptcha())) {
            loginInfo.setStatus(LoginInfo.STATUS_FAIL);
            loginInfo.setMsg("验证码错误");
            loginInfoService.insertLoginInfo(loginInfo);
            return AjaxResponse.error("验证码错误");
        }

        // 3. 验证用户名和密码
        User user = userService.findByUsernameForAuth(loginRequest.getUsername());
        if (user == null || !user.getEnabled()) {
            loginInfo.setStatus(LoginInfo.STATUS_FAIL);
            loginInfo.setMsg("用户名或密码错误");
            loginInfoService.insertLoginInfo(loginInfo);
            return AjaxResponse.error("用户名或密码错误");
        }

        if (user.getLocked()) {
            loginInfo.setStatus(LoginInfo.STATUS_FAIL);
            loginInfo.setMsg("账户已被锁定");
            loginInfoService.insertLoginInfo(loginInfo);
            return AjaxResponse.error("账户已被锁定");
        }

        if (!userService.validatePassword(loginRequest.getPassword(), user.getPassword())) {
            // 增加失败次数并获取当前失败次数
            Long attempts = incrementLoginAttempts(loginRequest.getUsername());

            // 如果失败次数达到限制，锁定账户
            if (attempts >= smartHomeConfig.getSecurity().getLogin().getMaxAttempts()) {
                redisTemplate.opsForValue().set(lockKey, "locked",
                        smartHomeConfig.getSecurity().getLogin().getLockDurationSeconds(), TimeUnit.SECONDS);
                loginInfo.setStatus(LoginInfo.STATUS_FAIL);
                loginInfo.setMsg("登录失败次数过多，账户已被锁定");
                loginInfoService.insertLoginInfo(loginInfo);
                return AjaxResponse.error("登录失败次数过多，账户已被锁定");
            }

            loginInfo.setStatus(LoginInfo.STATUS_FAIL);
            loginInfo.setMsg("用户名或密码错误");
            loginInfoService.insertLoginInfo(loginInfo);
            return AjaxResponse.error("用户名或密码错误");
        }

        // 登录成功，清除失败记录
        clearLoginAttempts(loginRequest.getUsername());

        // 4. 生成JWT token
        Authentication authentication = createAuthenticationToken(user);
        String jwt = tokenProvider.createToken(authentication, loginRequest.isRememberMe());

        // 5. 记录登录成功日志
        loginInfo.setStatus(LoginInfo.STATUS_SUCCESS);
        loginInfo.setMsg("登录成功");
        loginInfoService.insertLoginInfo(loginInfo);

        // 6. 返回登录成功信息
        Map<String, Object> data = new HashMap<>();
        data.put("token", jwt);
        data.put("username", user.getUsername());

        return AjaxResponse.success("登录成功", data);
    }

    @GetMapping("/captcha")
    public AjaxResponse<Map<String, String>> getCaptcha(HttpSession session) throws IOException {
        String captchaKey = UUID.randomUUID().toString();
        String capText = captchaProducer.createText();

        // 存入 Redis，设置 5 分钟过期
        redisTemplate.opsForValue().set("captcha:" + captchaKey, capText, 5, TimeUnit.MINUTES);

        // 生成验证码图片
        BufferedImage bi = captchaProducer.createImage(capText);

        // 将图片转换为Base64
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(bi, "jpg", outputStream);
        String base64Image = Base64.getEncoder().encodeToString(outputStream.toByteArray());

        // 构建返回数据
        Map<String, String> response = new HashMap<>();
        response.put("captcha", "data:image/jpeg;base64," + base64Image);
        response.put("key", captchaKey);

        return AjaxResponse.success(response);
    }

    @PostMapping("/validate-captcha")
    public AjaxResponse<?> validateCaptcha(@RequestBody CaptchaRequest request) {
        String storedCaptcha = redisTemplate.opsForValue().get("captcha:" + request.getKey());

        if (storedCaptcha == null) {
            return AjaxResponse.error("验证码已过期");
        }

        boolean isValid = storedCaptcha.equalsIgnoreCase(request.getCaptcha());

        if (isValid) {
            Map<String, Object> data = new HashMap<>();
            data.put("valid", true);
            return AjaxResponse.success("验证码正确", data);
        } else {
            return AjaxResponse.error("验证码错误");
        }
    }

    @Log(title = "用户登出", businessType = 0)
    @PostMapping("/logout")
    public AjaxResponse<?> logout(@RequestHeader("Authorization") String authHeader, HttpServletRequest request) {
        log.debug("处理当前会话的登出请求");

        // 解析User-Agent
        String userAgentStr = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgentUtil.parse(userAgentStr);

        LoginInfo loginInfo = new LoginInfo();
        loginInfo.setIpaddr(getIpAddr(request));
        loginInfo.setLoginLocation(getIpLocation(loginInfo.getIpaddr()));
        loginInfo.setBrowser(userAgent.getBrowser().getName());
        loginInfo.setOs(userAgent.getOs().getName());
        loginInfo.setLoginTime(new Date());

        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.substring(7);

            try {
                // 检查token是否有效
                if (tokenProvider.validateToken(token)) {
                    // 获取用户名
                    String username = tokenProvider.getUsernameFromToken(token);
                    log.info("用户 {} 正在登出当前会话", username);

                    // 记录登出信息
                    loginInfo.setLoginName(username);
                    loginInfo.setStatus(LoginInfo.STATUS_SUCCESS);
                    loginInfo.setMsg("用户登出成功");
                    loginInfoService.insertLoginInfo(loginInfo);

                    // 将token加入黑名单，仅影响当前会话
                    tokenProvider.addToBlacklist(token);

                    return AjaxResponse.success("当前会话已登出");
                }
            } catch (Exception e) {
                log.error("登出过程中发生错误", e);
                loginInfo.setStatus(LoginInfo.STATUS_FAIL);
                loginInfo.setMsg("登出过程发生错误：" + e.getMessage());
                loginInfoService.insertLoginInfo(loginInfo);
            }
        } else {
            loginInfo.setStatus(LoginInfo.STATUS_FAIL);
            loginInfo.setMsg("无效的认证信息");
            loginInfoService.insertLoginInfo(loginInfo);
        }

        // 即使token无效或发生错误，也返回登出成功
        // 不暴露错误细节，保持一致的用户体验
        return AjaxResponse.success("登出成功");
    }

    private Authentication createAuthenticationToken(User user) {
        // 创建已认证的token，不包含密码信息
        return new UsernamePasswordAuthenticationToken(
                user.getUsername(),    // principal: 用户标识
                null,                  // credentials: 已认证后不需要携带密码
                userService.getUserAuthorities(user.getId())  // authorities: 用户权限
        );
    }
}

